library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.all;
use constants.all;

entity cpu is
	generic(numRegs : natural range 2 to 32 := 4; PC_INCREMENT : natural := 1);
	port(
	   clock    : in std_logic;
      resetN    : in std_logic;
		
		-- instruction memory/cache interface
        imemAddr : out b32;               
        imemData : in b32;
        
		-- data memory/cache interface		
		dmemAddrRead : out b32;
		dmemAddrWrite : out b32;       
		dmemWrEna   : out std_logic; -- '1' to write(store) or '0' to read(load)
        dmemDataRead : in b32;
		dmemDataWrite : out b32;
		dmemRdEna   : out std_logic; -- '1' to write(store) or '0' to read(load)
        dmemNByteMask : out std_logic_vector(3 downto 0); -- selection fo bytes to read/write
        doWait : in std_logic
		);
end cpu;

architecture arch of cpu is	
	type RegFileType is array(0 to numRegs - 1) of b32;
	signal reg : RegFileType; -- register file

	signal PC : b32; -- program counter
		
	alias irCond : CondType is imemData(31 downto 28);
	alias irClass : ClassType is imemData(27 downto 26);
	alias irFreeForWhatever : std_logic_vector(3 downto 0) is imemData(25 downto 22);
	alias irFunc : FuncType is imemData(21 downto 16);		
	alias irImmB : std_logic is imemData(15);
	alias irSelA : RegSelType is imemData(14 downto 10);
	alias irSelB : RegSelType is imemData(9 downto 5);
	alias irSelDest : RegSelType is imemData(4 downto 0);
	
	-- for the ALU_Operation irFunc has a special meaning
	alias irAluOp : AluOpType is irFunc(4 downto 0);
	alias irAluWrBack : std_logic is irFunc(5);	    
	
	signal aluA, aluB, aluResult : b32;	
	signal aluFlagZ, aluFlagN, aluFlagC : std_logic;
	signal aluOp : AluOpType;
	
	-- stage 2 registers
	signal s2_RegSelDest : RegSelType;
	signal s2_class : ClassType;
	signal s2_func : FuncType;
	signal s2_exec : std_logic;
	
begin 		
	
	aluC: entity work.alu port map(aIn => aluA, bIn => aluB, instr => aluOp, 
		result => aluResult, flagZero => aluFlagZ, flagNeg => aluFlagN, flagOverflow => aluFlagC);
				
	-------[stage 1]--------------------------------------------------------
	stage1: process(clock, resetN, imemData, aluResult)		
		variable opA, opB, nextPC : b32;
	
		-- this procedure initializes the pipeline to a definitive state
		procedure s1_init is 
		begin			
			-- NOP on reset
            s2_exec <= '0';
            aluA <= (others => '-');
            aluB <= (others => '-');
			dmemAddrRead <= (others => '-');
			dmemAddrWrite <= (others => '-');
			dmemDataWrite <= (others => '-');
        	dmemWrEna <= '0'; -- disable writing to memory memory
			dmemRdEna <= '0'; -- disable reading from memory     	
			dmemNByteMask <= (others => '-');
		end s1_init;													   
		
		-- this function determines if the instruction will be executed
		impure function shouldExecInstruction return std_logic is
		begin
			if (irCond = COND_ALWAYS) then --independent from previous calculations
				return '1';
			elsif 	     -- now we assume a previous substraction a-b
	           --for a == b 
	           ((irCond(2 downto 0) = C_EQ) and (aluFlagZ = '1')) or  
	           --for a < b
				  ((irCond(2 downto 0) = C_LESS) and (aluFlagN = '1')) or
	           --for a > b
				  ((irCond(2 downto 0) = C_GREATER) and (aluFlagN = '0' and aluFlagZ = '0')) or
	           --if overflow occures
				  ((irCond(2 downto 0) = C_CARRY) and (aluFlagC = '1')) then
				return not irCond(3);
			else
	          -- inverse meaning of the above:
	          -- not equal, greater or equal, less or equal, no overflow occured
				return irCond(3);
			end if;
		end function;	
		
	begin		
		if rising_edge(clock) then
            s1_init;
			aluOp <= irAluOp;
			s2_class <= irClass;					
			s2_regSelDest <= irSelDest;			
			s2_func <= irFunc;           
            			
			if resetN = '0' then -- Reset
				nextPC := (others => '0');							
			elsif doWait = '1' then -- Pipeline Bubble				
				nextPC := PC; --PC;			                
			elsif shouldExecInstruction = '1' then -- execute Instruction
                s2_exec <= '1';
                
				-- if B was used as a Destionation in a previous Alu instruction, get the result from the alu
				if s2_class = CLASS_ALU and irImmB = '0' and irSelB = s2_regSelDest then
					opB := aluResult;
				else					
					if irImmB = '1' then 
						-- sign extend opB to the MSB of irSelB
						-- for example 10011 is extended to 111111111111111110011
						opB(opB'length - 1 downto irSelB'length) := (others => irSelB(4));
						opB(irSelB'length - 1 downto 0) := irSelB;
					else
						opB := reg(to_integer(unsigned(irSelB)));
					end if;
				end if;
				
				-- if A was used as a Destionation in a previous Alu instruction, get the result from the alu
				if s2_class = CLASS_ALU and irSelA = s2_regSelDest and irAluOp /= ALU_PASS_B then
					opA := aluResult;
				else
					opA := reg(to_integer(unsigned(irSelA)));
				end if;				
                aluA <= opA;
			    aluB <= opB;
                
                
				-- jump opcodes
				if (irClass = CLASS_JUMP) then -- jumps
					if(irFunc = F_JUMP_REL) then
						nextPC := std_logic_vector(unsigned(PC) + unsigned(opB));
					else --if(irFunc = F_JUMP_ABS) then -- F_JUMP_ABS
						nextPC := opB;
					end if;
                else
                    nextPC := std_logic_vector(unsigned(PC) + PC_INCREMENT);					
                end if;    
                
                -- load/store opcodes
    			if irClass = CLASS_LDST then 
					if irFunc = F_LDST_LOAD then					
						dmemAddrRead <= opA;
						dmemNByteMask <= opB(3 downto 0);
						dmemRdEna <= '1';		
						dmemWrEna <= '0';				
					elsif irFunc = F_LDST_STORE then
						dmemAddrWrite <= opA;
						dmemNByteMask <= irSelDest(3 downto 0);
						dmemDataWrite <= opB;
						dmemRdEna <= '0';		
						dmemWrEna <= '1';				   
					end if;
				end if;															
			else -- Condition failed
				nextPC := std_logic_vector(unsigned(PC) + PC_INCREMENT);				
			end if;									
            
            --report "PC: " & integer'image(to_integer(unsigned(PC))) & 

			PC <= nextPC;
			imemAddr <= nextPC;	
            
		end if;
	end process stage1;
	----------[stage 2]---------------------
	stage2: process(clock, resetN, aluResult, dmemDataRead, s2_class, s2_func, s2_exec)
	begin
		if rising_edge(clock) then
			if resetN = '1' and s2_exec = '1' then
       			if(s2_class = CLASS_ALU) and s2_func(5) = '1' then
       				reg(to_integer(unsigned(s2_regSelDest))) <= aluResult;							
       			elsif s2_class = CLASS_LDST and s2_func = F_LDST_LOAD then
       				reg(to_integer(unsigned(s2_regSelDest))) <= dmemDataRead;					     				
       			end if;	
            end if;
		end if;	
	end process stage2;
end arch;		
